﻿//////////////////////////////////////////////////////////////////////////////
//
//  Copyright © 1998-2024 Glodon Company Limited.  All rights reserved.
//
//  Use of this software is subject to the terms of the Glodon license
//  agreement provided at the time of installation or download, or which
//  otherwise accompanies this software in either electronic or hard copy form.  
//
//////////////////////////////////////////////////////////////////////////////

// Reviewed

#pragma once
#include "GcmpGeometryInterface.h"
#include "BodyTopologyOperateData.h"
#include "GeometryErrorCode.h"
#include "OffsetWireBodyOptions.h"
#include "BlendFixOptions.h"

namespace gcmp
{
    class IPlane;
    class ISurface;
    class IPolygon;
    class IEdge;
    class IGraphicsBRepBody;


    /// \brief 体的拓扑操作类，用于实现体的拓扑操作
    ///
    class GCMP_GEOMETRY_INTERFACE_EXPORT AlgorithmBodyTopologyOperate
    {
    public:

        /// \brief 构造布尔体（包含交、并、差）
        /// \param pFirstBody                              第一个体, 布尔差时为被扣减体(blank body)
        /// \param pSecondBody                             第二个体, 布尔差时为扣减体(tool body)
        /// \param booleanOperation                        布尔操作类型
        /// \param tolerance                               容差，默认0.1
        /// \return 结果体
        static OwnerPtr<IBody> BoolOperate(
            const IBody *pFirstBody,
            const IBody *pSecondBody,
            BodyBooleanOperation booleanOperation,
            double tolerance=0.1
        );

        /// \brief 构造布尔体（包含交、并、差）, 返回Face,Edge布尔运算前后映射关系
        /// \param pFirstBody                              第一个体, 布尔差时为被扣减体(blank body)
        /// \param pSecondBody                             第二个体, 布尔差时为扣减体(tool body)
        /// \param booleanOperation                        布尔操作类型
        /// \param resultFaceSourceData                    面的追溯数据
        /// \param resultEdgeSourceData                    边的追溯数据
        /// \param tolerance                               容差，默认0.1
        /// \return 结果体
        static OwnerPtr<IBody> BoolOperate(
            const IBody *pFirstBody,
            const IBody *pSecondBody,
            BodyBooleanOperation booleanOperation,
            std::vector<BoolResultFaceSourceData>& resultFaceSourceData,
            std::vector<BoolResultEdgeSourceData>& resultEdgeSourceData,
            double tolerance = 0.1
        );


        /// \brief （包含交、并、差）,返回布尔操作结果类型、结果体和Face,Edge布尔运算前后映射关系
        /// \param pFirstBody                              第一个体, 布尔差时为被扣减体(blank body)
        /// \param pSecondBody                             第二个体, 布尔差时为扣减体(tool body)
        /// \param booleanOperation                        布尔操作类型
        /// \param resultFaceSourceData                    面的追溯数据
        /// \param resultEdgeSourceData                    边的追溯数据
        /// \param resultBody                              布尔操作结果体
        /// \param tolerance                               容差，默认0.1
        /// \return BoolOperateErrorStatus                  布尔操作结果类型

        static BoolOperateErrorStatus BoolOperate(
            const IBody* pFirstBody,
            const IBody* pSecondBody,
            BodyBooleanOperation booleanOperation,
            std::vector<BoolResultFaceSourceData>& resultFaceSourceData,
            std::vector<BoolResultEdgeSourceData>& resultEdgeSourceData,
            OwnerPtr<IBody>& resultBody,
            double tolerance=0.1     
        ) ;

        /// \brief 平面切体
        /// \param pToolPlane       切平面
        /// \param pBlankBody       被切割体
        /// \param opPositiveBody   平面法向正向的体
        /// \param opNegativeBody   平面法向反向的体
        /// \param tolerance        容差，默认0.1
        /// \return 成功返回true，失败返回false 
        static bool Split(
            const IPlane* pToolPlane,
            const IBody* pBlankBody,
            OwnerPtr<IBody> &opPositiveBody, 
            OwnerPtr<IBody> &opNegativeBody,
            double tolerance = 0.1
        );     
        
        /// \brief 平面切体       
        /// \param pBlankBody                被切割体
        /// \param pToolPlane                切平面
        /// \param opResultGBody             平面法向负向的体对象(带有拓扑命名信息)
        /// \param resultFaceSourceData      面的追溯数据
        /// \param resultEdgeSourceData      边的追溯数据
        /// \param tolerance                 容差，默认0.1
        /// \param ppResultPositiveGBody 平面法向正向的体对象(带有拓扑命名信息)
        /// \param pPositiveFaceSourceData ppResultPositiveGBody的面的追溯数据
        /// \param pPositiveEdgeSourceData ppResultPositiveGBody的边的追溯数据
        /// \return EnPlaneSplitBodyResult   面裁体结果
        static EnPlaneSplitBodyResult Split(
            const IBody* pBlankBody,
            const IPlane* pToolPlane,
            OwnerPtr<IBody>& opResultGBody,
            std::vector<BoolResultFaceSourceData>& resultFaceSourceData,
            std::vector<BoolResultEdgeSourceData> &resultEdgeSourceData,
            double tolerance = 0.1,
            OwnerPtr<IBody>* ppResultPositiveGBody = nullptr,
            std::vector<BoolResultFaceSourceData>* pPositiveFaceSourceData = nullptr,
            std::vector<BoolResultEdgeSourceData>* pPositiveEdgeSourceData = nullptr        
        );  

        /// \brief  平面分割体，返回分割后的平面截面和平面截面上每条边对应被分割体上面的索引
        /// \param pToolPlane 分割平面
        /// \param pBodyToSplit 被分割的体
        /// \param opPolygon 分割后的平面截面
        /// \param pFaceIndexVec 二维数组，第一维代表平面截面opPolygon中LOOP的索引，第二维代表LOOP中边的索引，数组中存放的值代表分割体pBodyToSplit上面的索引。以pFaceIndexVec[0][1]==3为例，表示所在Polygon中，存在索引为0的loop，该loop中索引为0的边在pBodyToSplit的拓扑值为3。pFaceIndexVec可以为空指针nullptr，如果传入的为空指针则代表不做计算
        /// \return 返回true表示计算成功，若分割后的平面截面为 nullptr 表示没有切开，返回 false 表示计算失败，且分割后的平面截面参数为 nullptr
        static bool SliceBody(const IPlane* pToolPlane, const IBody* pBodyToSplit, OwnerPtr<IPolygon>& opPolygon, std::vector<std::vector<int>>* mapFaceIndex);

        /// \brief 平面切体，获取切分后的截面(ShellBody，这个ShellBody只有一个Face)
        /// \param pToolPlane      切平面
        /// \param pBlankBody      待切分的体
        /// \return 切分后的截面
        static OwnerPtr<IBody> SplitBodyAndGetSectionShellBody(const IPlane* pToolPlane, const IBody* pBlankBody);

        
        /// \brief 平面切壳体
        /// \param pToolPlane      切平面
        /// \param pBlankShell     待切分的壳
        /// \param opPositiveShell 平面法向正向的壳
        /// \param opNegativeShell 平面法向负向的壳
        /// \return 成功返回true，失败返回false
        static bool SplitShell(
            const IPlane* pToolPlane,
            const IBody* pBlankShell,
            OwnerPtr<IBody> &opPositiveShell, 
            OwnerPtr<IBody> &opNegativeShell
        );

        /// \brief 壳切体
        /// \param pToolShell 壳(可以不封闭，根据法向判断内外)
        /// \param pBlankBody 待切分的体
        /// \param opPositiveBody 壳外的体
        /// \param opNegativeBody 壳内的体
        /// \return 成功返回true，失败返回false
        static bool ShellSplitBody(
            const IBody* pToolShell,
            const IBody* pBlankBody,
            OwnerPtr<IBody> &opPositiveBody,
            OwnerPtr<IBody> &opNegativeBody
        );

        /// \brief 壳体求差
        /// \param pShell       壳(可以不封闭，有内外)
        /// \param pBody        待扣减的体
        /// \param opRultShell  结果壳
        /// \return 计算成功返回true;计算失败返回false
        static bool ShellSubtractBody(const IBody* pShell, const IBody* pBody, OwnerPtr<IBody> &opRultShell);

        /// \brief 壳体求交
        /// \param pShell       壳(可以不封闭，有内外)
        /// \param pBody        待扣减的体
        /// \param opRultShell  结果壳
        /// \return 计算成功返回true;计算失败返回false
        static bool ShellIntersectBody(const IBody* pShell, const IBody* pBody, OwnerPtr<IBody> &opRultShell);

        /// \brief 壳切分壳
        /// \param pToolShell   切分壳(可以不封闭，有内外)
        /// \param pBlankShell  待切分的壳
        /// \param opPositiveShell 切分壳外的壳
        /// \param opNegativeShell 切分壳外的壳
        /// \return 成功返回true，失败返回false
        static bool ShellSplitShell(const IBody* pToolShell, const IBody* pBlankShell, OwnerPtr<IBody> &opPositiveShell, OwnerPtr<IBody> &opNegativeShell);

        /// \brief 构造壳（包含交、并、差）
        /// \param pFirstBody                              第一个体, 壳差时为被扣减体(blank body)
        /// \param pSecondBody                             第二个体, 壳差时为扣减体(tool body)
        /// \param booleanOperation                        布尔操作类型
        /// \return 结果体
        static OwnerPtr<IBody> ShellOperate( const IBody *pFirstBody, const IBody *pSecondBody, BodyBooleanOperation booleanOperation );        
        /// \brief 壳扣壳
        /// \param pFirstShell1 第一个壳
        /// \param pSecondShell 第二个壳
        /// \return 结果壳
        static OwnerPtr<IBody> ShellSubtractShell(const IBody* pFirstShell1, const IBody* pSecondShell);

        /// \brief 壳交壳
        /// \param pFirstShell1 第一个壳
        /// \param pSecondShell 第二个壳
        /// \return 结果壳
        static OwnerPtr<IBody> ShellIntersectShell(const IBody* pFirstShell1, const IBody* pSecondShell);

        /// \brief 壳合并
        /// \param pFirstShell1 第一个壳
        /// \param pSecondShell 第二个壳
        /// \return 结果壳
        static OwnerPtr<IBody> ShellUniteShell(const IBody* pFirstShell1, const IBody* pSecondShell);

        /// \brief 切分体上的一条边
        /// \param pBody     体
        /// \param edgeIndex 待切分边的序号
        /// \param point     切分点
        /// \param sourceToTargetEdgeIndexMap 切分前后体上边的映射
        /// \return 边切分后的体
        static OwnerPtr<IBody> SplitEdge(const IBody* pBody, int edgeIndex, Vector3d point,
            std::multimap<int, int>& sourceToTargetEdgeIndexMap);

        /// \brief 计算两个体的消隐线
        /// \param pFirstBody      第一个体
        /// \param pSecondBody     第二个体
        /// \param opFirstNewBody  第一个体经消隐操作生成的体
        /// \param opSecondNewBody 第二个体经消隐操作生成的体
        /// \param firstEdgeSourceStructs 第一个体消隐操作前后，边序号的来源边，是否是split edge
        /// \param secondEdgeSourceStructs 第二个体消隐操作前后，边序号的来源边，是否是split edge
        /// \param hiddenEdgeIndexMap     第一个体与第二个体消隐边序号的对应
        /// \return 成功返回true，失败返回false
        static bool CalculateHiddenEdges(const IBody* pFirstBody, const IBody* pSecondBody,
            OwnerPtr<IBody>& opFirstNewBody, OwnerPtr<IBody>& opSecondNewBody,
            std::vector<HiddenEdgeSourceData>& firstEdgeSourceStructs,
            std::vector<HiddenEdgeSourceData>& secondEdgeSourceStructs,
            std::map<int, int>& hiddenEdgeIndexMap);

        /// \brief 计算两个体的消隐线, 直接修改输入体，内部不拷贝体
        /// \param pFirstBody      第一个体
        /// \param pSecondBody     第二个体
        /// \param firstEdgeSourceStructs 第一个体消隐操作前后，边序号的来源边，是否是split edge
        /// \param secondEdgeSourceStructs 第二个体消隐操作前后，边序号的来源边，是否是split edge
        /// \param hiddenEdgeIndexMap     第一个体与第二个体消隐边序号的对应
        /// \return 成功返回true，失败返回false
        static bool CalculateHiddenEdges(IBody* pFirstBody, IBody* pSecondBody,
            std::vector<HiddenEdgeSourceData>& firstEdgeSourceStructs,
            std::vector<HiddenEdgeSourceData>& secondEdgeSourceStructs,
            std::map<int, int>& hiddenEdgeIndexMap);

        /// \brief 计算墙墙X型连接的消隐线
        /// \param pToolBody  墙墙连接的切体
        /// \param pBlankBody 墙墙连接的被切体
        /// \param opNewBody  经消隐操作生成的切体
        /// \param hiddenEdgeSourceData 切体的消隐边的追溯数据（边序号的来源边，是否是splitedge）
        /// \param cuttingBodyHiddenEdgeIndex 切体中需要消隐边的序号
        /// \return 成功返回true，失败返回false
        static bool CalculateWallCrossedJoinHiddenEdges(const IBody* pCuttingBody, const IBody* pCuttedBody,
            OwnerPtr<IBody>& opNewCuttingBody,
            std::vector<HiddenEdgeSourceData>& hiddenEdgeSourceData,
            std::vector<int>& cuttingBodyHiddenEdgeIndex);

        
        /// \brief 检测接口FitBodyToTargetedFaceWithoutTopoChange输入数据的有效性
        /// \param pTargetedSurface                      目标曲面
        /// \param pOriginalBody                         原始体
        /// \param pTractiveFaceOnOriginalBody           体上指定的牵引面
        /// \param bIsExtendedFirst                      是否延伸端优先
        /// \return 数据有效返回true,无效返回false
        static bool IsValidForFitBodyToTargetedFaceWithoutTopoChange(
            const ISurface* pTargetedSurface,
            const IBody* pOriginalBody,
            const IFace* pTractiveFaceOnOriginalBody,
            bool bIsExtendedFirst
        );

        
        /// \brief 在体上指定牵引面，将体贴合到目标面，以实现体的延伸或裁剪
        ///  算法使用：1、输入的体上的所有面面和目标面的面类型只能为平面或者圆柱面；
        ///  2、目标面（延伸后目标面）必须与输入的体相交或者与输入的体延牵引面的延伸段相交；
        ///  3、用户保证贴合前后体的拓扑不变，即体上Face和Edge数量及相互位置关系不变，否则算法报错,不对输入的体做修改。
        /// \param pTargetedSurface                      目标曲面
        /// \param pOriginalBody,                              原始体
        /// \param pTractiveFaceOnOriginalBody                     体上指定的牵引面
        /// \param bIsExtendedFirst                                   是否延伸端优先
        /// \return 计算成功返回true,失败返回false(返回false时，体可能变成无效的体，需要调用者自行回滚)
        static bool FitBodyToTargetedFaceWithoutTopoChange(
            const ISurface* pTargetedSurface,
            IBody* pOriginalBody,
            IFace* pTractiveFaceOnOriginalBody,
            bool bIsExtendedFirst
        );

        /// \brief 在体上指定牵引面，将体贴合到目标面，以实现体的延伸或裁剪
        ///     算法使用：1、输入的体上的所有面面和目标面的面类型只能为平面或者圆柱面；
        ///              2、目标面（只能是平面）必须与输入的体相交或者与输入的体延牵引面的延伸段相交；
        ///              3、用户保证贴合前后体的拓扑不变，即体上Face和Edge数量及相互位置关系不变，否则算法报错,不对输入的体做修改。
        /// \param pTargetPlane                  目标平面
        /// \param pOriginalBody,                原始体
        /// \param pTractiveFaceOnOriginalBody   体上指定的牵引面
        /// \return 计算成功返回true,失败返回false(返回false时，体可能变成无效的体，需要调用者自行回滚)
        static bool FitBodyToTargetPlaneAlongVAxisWithoutTopoChange(
            const IPlane* pTargetPlane,
            IBody* pOriginalBody,
            IFace* pTractiveFaceOnOriginalBody
        );        
        
        /// \brief 合并邻接的重合面
        /// \param opBody      待合并面的体
        /// \return 成功返回true,失败返回false
        static bool MergeCoincideFace(OwnerPtr<IBody> &opBody);

        
        /// \brief  合并体
        /// \param opResultGody                    合并到目标体
        /// \param pBodiesToBeMerge                待被合并的体
        /// \return 成功返回true,失败返回false
        static bool MergeBody(OwnerPtr<IBody> &opResultGody, const std::vector<const IBody*>& pBodiesToBeMerge);

        /// \brief  合并Brep体的Faces
        /// \param  pBody 待合并的体
        /// \param  dDistEpsilon  容差含义： 距离容差，一般建议使用默认角度容差
        /// \param  dAngleEpsilon 容差含义： 角度容差；一般建议使用默认角度容差
        /// \return 如果面有合并，则返回 true. 没有变化返回 false`
        static bool MergeFaces(IBody* pBody, double dDistEpsilon = 0.0001, double dAngleEpsilon = 0.00005);

        /// \brief  合并Brep体的Edges
        /// \param  pBody 待合并的体
        /// \param  dDistEpsilon     容差含义： 距离容差，一般建议使用默认角度容差
        /// \param  dAngleEpsilon    容差含义： 角度容差；一般建议使用默认角度容差
        /// \return 如果边有合并，则返回 true. 没有变化返回 false
        static bool MergeEdges(IBody* pBody, double dDistEpsilon = 0.0001, double dAngleEpsilon = 0.00005);

        /// \brief   平移Brep体给定面。
       /// \param  pFace      待平移的面，要求其所在的Brep体是有效的
       /// \param  rVector    平移向量
       /// \param   resultBody   新生成的Brep体
       /// \return   错误码
        static GeometryErrorCode MoveFace(const IFace* pFace, const Vector3d& rVector, OwnerPtr<IBody>& resultBody);

        /// \brief  偏移Brep体给定面
        /// \param[in] pFace  待偏移的面，要求其所在的Brep体是有效的
        /// \param[in] offset  偏移距离（可为负值，表示按面法向的负向偏移）
        /// \param[out] resultBody  结果Brep体
        /// \return 错误码
        static GeometryErrorCode OffsetFace(const IFace* pFace, double offset, OwnerPtr<IBody>& opOffsetBody);

        /// \brief  偏移Brep体给定面
        /// \param[in] wireBody  需要偏移的共面的wirebody
        /// \param[in] supportBody  WireBody偏移前后所在的体，类型为壳体或实体。可为空 
        /// \param[in] option  偏移选项： 
        ///                     偏移距离:大于0，正向偏移，小于0，负向偏移。（正向：平面法向与曲线切向的叉乘为正向偏移）
        ///                     平面法向:wirebody平面法向
        ///                     连接方式：默认延长求交
        ///                     容差
        /// \param[out] opOffsetBody  输出结果体WireBody
        /// \return 错误码
        static GeometryErrorCode OffsetWireBody(const IBody* wireBody, const IBody* supportBody, const WireBodyOffsetOptions &option, OwnerPtr<IBody>& opOffsetBody);

        /// \brief  将给定Sheet壳体的面补全，生成Solid实体
        /// \param[in] pSheetBody  待补面的壳体
        /// \param[out] opFilledBody  填充的Solid实体
        /// \param[in] dDistEpsilon  缝合容差
        /// \return 错误码
        static GeometryErrorCode FillFace(const IBody* pSheetBody, OwnerPtr<IBody>& opFilledBody, double dDistEpsilon = 0.0001);

        /// \brief  常半径倒圆角
        /// \param[in] edgeLists  用户输入的圆角边列表，所有的edges需要在同一个体上
        /// \param[in] options  用户指定的圆角功能选项（该选项暂不支持，先占位，默认构造即可）
        /// \param[in] radius  圆角半径
        /// \param[out] resultInfo  倒角的结果信息
        ///             resultInfo.ResultBody  新生成的带圆角的体
        ///             后面用于扩展一些错误信息，拓扑追踪等
        /// \return 错误码
        static BlendErrorCode FixConstantBlends(const std::vector<const IEdge*>& edgeLists, gcmp::BlendFixOptions& options, double radius, gcmp::BlendResultInfo& resultInfo);

        /// \brief  通过距离倒斜角
        ///         目前仅支持等距倒斜角，即range1 等于 range2，faceLists输入为空列表
        /// \param[in] edgeLists  用户输入的倒角边列表，所有的edges需要在同一个体上
        /// \param[in] faceLists  不等距倒角的range1所在的面列表,等距倒角可输入空列表
        /// \param[in] range1  倒角距离 1，等距倒角range1等于range2，不等距倒角faceLists不能为空
        /// \param[in] range2  倒角距离 2
        /// \param[in] options  用户指定的倒角功能选项（该选项暂不支持，先占位，默认构造即可）
        /// \param[out] resultInfo  倒角的结果信息
        ///             resultInfo.ResultBody  新生成的倒角的体
        ///             后面用于扩展一些错误信息，拓扑追踪等
        /// \return 错误码
        static BlendErrorCode FixChamferBlends(const std::vector<const IEdge*>& edgeLists, const std::vector<const IFace*>& faceLists, double range1, double range2, gcmp::BlendFixOptions& options, gcmp::BlendResultInfo& resultInfo);

        /// \brief 开启FindCoinEdges的RTree缓存
        /// \param
        /// \return
        static void BeginRTreeCacheForFindCoinEdges();
        /// \brief 结束时清理FindCoinEdges的RTree缓存
        /// \param
        /// \return
        /// \remark 由于相关的函数设计上没有考虑批量处理，接口都是单个GBody，所以没法在内部控制缓存，要调整的话工作量有点大
        static void EndRTreeCacheForFindCoinEdges();

        static void RemoveRTreeCacheForFindCoinEdges(IGraphicsBRepBody* pBody);
        /*!
        * @brief   印刻：用给定Wire分割给定面。该面及面所在的Brep体将被直接修改
        * @param[in]  pFace                待分割的面，要求其所在的Brep体是有效的
        * @param[in]  pSplitWire           用于分割的Wire
        * @param[in]  pBody                分割后的新的body
        * @param[in]  dDistEpsilon         求交距离容差
        * @return   印刻结果 
        */
        static bool Imprint(IFace* pFace, const IBody* pSplitWire, OwnerPtr<IBody>& pBody, double dDistEpsilon = 0.0001);
    };
}
